-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Enable OIDC Token Exchange for BYO-CIAM (R-GCIP) #14983
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
base: exchange-token-implementation
Are you sure you want to change the base?
Conversation
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback. |
Generated by 🚫 Danger |
|
||
/// Represents the result of a successful OIDC token exchange, containing a Firebase ID token | ||
/// and its expiration. | ||
public struct FirebaseToken: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same point in the TenantConfig PR, is the nesting (e.g. Auth.FirebaseToken
) intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolved
func exchangeToken(idToken: String, | ||
idpConfigId: String, | ||
useStaging: Bool = false, | ||
completion: @escaping (FirebaseToken?, Error?) -> Void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer if we don't introduce new API that uses completion handlers. As least initially, new async API should be async/await only unless there a reason for completion handler (e.g. objc support, etc.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok Nick, Noted!
(removed the completion handler method)
guard let _ = requestConfiguration.location, | ||
let _ = requestConfiguration.tenantId | ||
else { | ||
throw AuthErrorUtils.operationNotAllowedError(message: "R-GCIP is not configured.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this should instead cause a fatal error? When do we anticipate this case triggering, during development or possibly in live app's execution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be during development. Unless the developer pushes their app without testing :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I think it should be a fatal error then. That way it is very apparent that the developer's integration is incorrect.
|
||
/// Represents the result of a successful OIDC token exchange, containing a Firebase ID token | ||
/// and its expiration. | ||
public struct FirebaseToken: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
idToken: idToken, | ||
idpConfigID: idpConfigId, | ||
config: requestConfiguration, | ||
useStaging: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this value come from useStaging
above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
guard let _ = requestConfiguration.location, | ||
let _ = requestConfiguration.tenantId | ||
else { | ||
throw AuthErrorUtils.operationNotAllowedError(message: "R-GCIP is not configured.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be during development. Unless the developer pushes their app without testing :)
idToken: idToken, | ||
idpConfigID: idpConfigId, | ||
config: requestConfiguration, | ||
useStaging: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, won't we end up always using staging here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing that out, Pavan; was using this for testing and forgot to remove. I've cleaned it up now.
91c3e86
to
aebb8bb
Compare
5b157b6
to
2458702
Compare
Add OIDC token exchange methods for BYO-CIAM#
4b068b0
to
b7ab3aa
Compare
|
||
/// Represents the result of a successful OIDC token exchange, containing a Firebase ID token | ||
/// and its expiration. | ||
struct FirebaseToken: Sendable { | ||
/// The Firebase ID token string. | ||
public let token: String | ||
/// The date at which the Firebase ID token expires. | ||
public let expirationDate: Date | ||
|
||
init(token: String, expirationDate: Date) { | ||
self.token = token | ||
self.expirationDate = expirationDate | ||
} | ||
} | ||
|
||
/// Exchanges a third-party OIDC ID token for a Firebase ID token. | ||
/// | ||
/// This method is used for Bring Your Own CIAM (BYO-CIAM) in Regionalized GCIP (R-GCIP), | ||
/// where the `Auth` instance must be configured with a `TenantConfig`, including `location` | ||
/// and `tenantId`, typically by using `Auth.auth(app:tenantConfig:)`. | ||
/// | ||
/// Unlike standard sign-in methods, this flow *does not* create or update a `User`object and | ||
/// *does not* set `CurrentUser` on the `Auth` instance. It only returns a Firebase token. | ||
/// | ||
/// - Parameters: | ||
/// - oidcToken: The OIDC ID token obtained from the third-party identity provider. | ||
/// - idpConfigId: The ID of the Identity Provider configuration within your GCIP tenant | ||
/// - useStaging: A Boolean value indicating whether to use the staging Identity Platform | ||
/// backend. Defaults to `false`. | ||
/// - Returns: A `FirebaseToken` containing the Firebase ID token and its expiration date. | ||
/// - Throws: An error if the `Auth` instance is not configured for R-GCIP, if the network | ||
/// call fails, or if the token response parsing fails. | ||
func exchangeToken(idToken: String, idpConfigId: String, | ||
useStaging: Bool = false) async throws -> FirebaseToken { | ||
// Ensure R-GCIP is configured with location and tenant ID | ||
guard let _ = requestConfiguration.tenantConfig?.location, | ||
let _ = requestConfiguration.tenantConfig?.tenantId | ||
else { | ||
throw AuthErrorUtils.operationNotAllowedError(message: "R-GCIP is not configured.") | ||
} | ||
let request = ExchangeTokenRequest( | ||
idToken: idToken, | ||
idpConfigID: idpConfigId, | ||
config: requestConfiguration, | ||
useStaging: useStaging | ||
) | ||
do { | ||
let response = try await backend.call(with: request) | ||
return FirebaseToken( | ||
token: response.firebaseToken, | ||
expirationDate: response.expirationDate | ||
) | ||
} catch { | ||
throw error | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Represents the result of a successful OIDC token exchange, containing a Firebase ID token | |
/// and its expiration. | |
struct FirebaseToken: Sendable { | |
/// The Firebase ID token string. | |
public let token: String | |
/// The date at which the Firebase ID token expires. | |
public let expirationDate: Date | |
init(token: String, expirationDate: Date) { | |
self.token = token | |
self.expirationDate = expirationDate | |
} | |
} | |
/// Exchanges a third-party OIDC ID token for a Firebase ID token. | |
/// | |
/// This method is used for Bring Your Own CIAM (BYO-CIAM) in Regionalized GCIP (R-GCIP), | |
/// where the `Auth` instance must be configured with a `TenantConfig`, including `location` | |
/// and `tenantId`, typically by using `Auth.auth(app:tenantConfig:)`. | |
/// | |
/// Unlike standard sign-in methods, this flow *does not* create or update a `User`object and | |
/// *does not* set `CurrentUser` on the `Auth` instance. It only returns a Firebase token. | |
/// | |
/// - Parameters: | |
/// - oidcToken: The OIDC ID token obtained from the third-party identity provider. | |
/// - idpConfigId: The ID of the Identity Provider configuration within your GCIP tenant | |
/// - useStaging: A Boolean value indicating whether to use the staging Identity Platform | |
/// backend. Defaults to `false`. | |
/// - Returns: A `FirebaseToken` containing the Firebase ID token and its expiration date. | |
/// - Throws: An error if the `Auth` instance is not configured for R-GCIP, if the network | |
/// call fails, or if the token response parsing fails. | |
func exchangeToken(idToken: String, idpConfigId: String, | |
useStaging: Bool = false) async throws -> FirebaseToken { | |
// Ensure R-GCIP is configured with location and tenant ID | |
guard let _ = requestConfiguration.tenantConfig?.location, | |
let _ = requestConfiguration.tenantConfig?.tenantId | |
else { | |
throw AuthErrorUtils.operationNotAllowedError(message: "R-GCIP is not configured.") | |
} | |
let request = ExchangeTokenRequest( | |
idToken: idToken, | |
idpConfigID: idpConfigId, | |
config: requestConfiguration, | |
useStaging: useStaging | |
) | |
do { | |
let response = try await backend.call(with: request) | |
return FirebaseToken( | |
token: response.firebaseToken, | |
expirationDate: response.expirationDate | |
) | |
} catch { | |
throw error | |
} | |
} | |
} | |
/// Exchanges a third-party OIDC ID token for a Firebase ID token. | |
/// | |
/// This method is used for Bring Your Own CIAM (BYO-CIAM) in Regionalized GCIP (R-GCIP), | |
/// where the `Auth` instance must be configured with a `TenantConfig`, including `location` | |
/// and `tenantId`, typically by using `Auth.auth(app:tenantConfig:)`. | |
/// | |
/// Unlike standard sign-in methods, this flow *does not* create or update a `User`object and | |
/// *does not* set `CurrentUser` on the `Auth` instance. It only returns a Firebase token. | |
/// | |
/// - Parameters: | |
/// - oidcToken: The OIDC ID token obtained from the third-party identity provider. | |
/// - idpConfigId: The ID of the Identity Provider configuration within your GCIP tenant | |
/// - useStaging: A Boolean value indicating whether to use the staging Identity Platform | |
/// backend. Defaults to `false`. | |
/// - Returns: A `FirebaseToken` containing the Firebase ID token and its expiration date. | |
/// - Throws: An error if the `Auth` instance is not configured for R-GCIP, if the network | |
/// call fails, or if the token response parsing fails. | |
func exchangeToken(idToken: String, idpConfigId: String, | |
useStaging: Bool = false) async throws -> FirebaseToken { | |
// Ensure R-GCIP is configured with location and tenant ID | |
guard let _ = requestConfiguration.tenantConfig?.location, | |
let _ = requestConfiguration.tenantConfig?.tenantId | |
else { | |
throw AuthErrorUtils.operationNotAllowedError(message: "R-GCIP is not configured.") | |
} | |
let request = ExchangeTokenRequest( | |
idToken: idToken, | |
idpConfigID: idpConfigId, | |
config: requestConfiguration, | |
useStaging: useStaging | |
) | |
do { | |
let response = try await backend.call(with: request) | |
return FirebaseToken( | |
token: response.firebaseToken, | |
expirationDate: response.expirationDate | |
) | |
} catch { | |
throw error | |
} | |
} | |
} | |
/// Represents the result of a successful OIDC token exchange, containing a Firebase ID token | |
/// and its expiration. | |
public struct FirebaseToken: Sendable { | |
/// The Firebase ID token string. | |
public let token: String | |
/// The date at which the Firebase ID token expires. | |
public let expirationDate: Date | |
init(token: String, expirationDate: Date) { | |
self.token = token | |
self.expirationDate = expirationDate | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also doesn't look quite right. The FirebaseToken should be moved outside of the Auth {}
scope. Left a suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with above suggestions
Description
This PR adds new public methods to
Auth
to support exchanging a third-party OIDC ID token for a Firebase ID token. This functionality is essential for the Bring Your Own CIAM (BYO-CIAM) feature, allowing integration with external OIDC providers within a Regionalized GCIP (R-GCIP) setup.New Public API:
struct FirebaseToken: Sendable
: Holds the exchangedtoken
(String) and itsexpirationDate
(Date).Auth.exchangeToken(customToken: String, idpConfigId: String, useStaging: Bool, completion: @escaping (FirebaseToken?, Error?) -> Void)
: Method to exchange the token, taking a completion handler.Auth.exchangeToken(customToken: String, idpConfigId: String, useStaging: Bool) async throws -> FirebaseToken
: Async/await version of the token exchange method.Details:
exchangeToken
methods use theExchangeTokenRequest
andExchangeTokenResponse
types to interact with the regionalizedidentityplatform.googleapis.com
backend.Auth
instance has been configured with aTenantConfig
(viaAuth.auth(app:tenantConfig:)
), aslocation
andtenantId
are necessary to construct the correct regional endpoint URL. An error is returned if these are not set.currentUser
on theAuth
instance. It purely exchanges a token.Introduces new public API surface to
FirebaseAuth
. This change depends on the previously introducedTenantConfig
,ExchangeTokenRequest
, andExchangeTokenResponse
.